""" 
>>> user1 = ARO.objects.create(parent_id=2, group='user1')
>>> root = ACO.objects.get(pk=1)
>>> check_permission(user1, root, 2)
-1
>>> place1 = ACO.objects.create(parent=root, element='place1')
>>> check_permission(user1, place1, 2)
-1
>>> user1.permission_set.create(aco=place1, action_id=2, flag=1)
<Permission: [ user1 ] -> place1 (ALLOW can_read)>
>>> check_permission(user1, root, 2)
-1
>>> check_permission(user1, place1, 2)
2
>>> check_permission(user1, place1, 1)
-1
>>> place2 = ACO.objects.create(parent=place1, element='place2')
>>> check_permission(user1, place2, 2)
2
"""
from django.db import models
from mptt.models import MPTTModel
from django.contrib.auth.models import User
from django.db import connection

# hook ARO manager onto User
def get_access_request_objects(self):
  if len(self.aro_set.all())>0:
    return self.aro_set.all()
  return ( ARO.objects.get(group='users'), )
User.get_access_request_objects = get_access_request_objects
  
class ACOManager(models.Manager):
  def match_path(self, parts):
    parent = ACO.objects.get(element="*")
    #cursor = connection.cursor()
    for part in parts:
      try:
        current = ACO.objects.get(parent=parent, element=part)
        parent = current
      except ACO.DoesNotExist:
        return parent
    return parent
    
  
class ARO(MPTTModel):
  parent = models.ForeignKey('self', null=True, blank=True, related_name='children')
  user = models.ForeignKey(User, blank=True, null=True)
  group = models.CharField(max_length=30, blank=True, db_index=True)
  control_objects = models.ManyToManyField('ACO', through="Permission", related_name="request_objects")
  
  class Meta:
    verbose_name = "access request object"
  
  def permission(self, aco, action_id):
    return check_permission(self, aco, action_id)
  
  def __unicode__(self):
    if self.group:
      return "[ %s ]" % self.group
    elif self.user_id: 
      return str(self.user)
    else:
      raise Exception, "No user or group defined for ARO"
  
class ACO(MPTTModel):
  parent = models.ForeignKey('self', null=True, blank=True, related_name='children')
  element = models.CharField(max_length=100, db_index=True)
  objects = ACOManager()
  
  class Meta:
    verbose_name = "access control object"
  
  def permission(self, aro, action_id):
    return check_permission(aro, self, action_id)
  
  def __unicode__(self):
    return self.element
    
class Action(models.Model):
  name = models.CharField(max_length=30, unique=True)
  
  def __unicode__(self):
    return self.name
    
class Permission(models.Model):
  PERMISSIONS = (
    (-1, "DENY"),
    (0, "-"),
    (1, "ALLOW"),
  )
  
  aro = models.ForeignKey(ARO)
  aco = models.ForeignKey(ACO)
  action = models.ForeignKey(Action)
  flag = models.SmallIntegerField(choices=PERMISSIONS, default=0)
  
  aro.verbose_name = 'user'
  aco.verbose_name = 'path'
    
  def __unicode__(self):
    return "%s -> %s (%s %s)" % (self.aro, self.aco, self.get_flag_display(), self.action)
    
_check_permission_sql = """SELECT flag, aco_level FROM %s AS aro_aco,
(
	SELECT node.id as aro_set_id, node.lft as aro_index
	FROM %s AS node
  WHERE node.lft<=%%s AND node.rght>=%%s AND tree_id=%%s
) AS aro_set,
(
	SELECT node.id as aco_set_id, node.lft as aco_index, node.level as aco_level
	FROM %s AS node
	WHERE node.lft<=%%s AND node.rght>=%%s AND tree_id=%%s
) AS aco_set
WHERE aro_aco.aro_id=aro_set.aro_set_id
AND aro_aco.aco_id=aco_set.aco_set_id
AND flag!=0
AND action_id=%%s
ORDER BY aco_set.aco_index desc, aro_set.aro_index desc
LIMIT 1
""" % ( Permission._meta.db_table, ARO._meta.db_table, ACO._meta.db_table )
    
def check_permission(aro, aco, action_id):
  cursor = connection.cursor()
  
  cursor.execute(_check_permission_sql, (aro.lft, aro.rght, aro.tree_id, aco.lft, aco.rght, aco.tree_id, action_id))
  row = cursor.fetchone()
  if row == None: return -1
  return row[0] * (row[1]+1)

_action_cache = {}
  
def has_permission(user, aco, permission):
  current = 0
  
  if not permission in _action_cache:
    try:
      action = Action.objects.get(name=permission)
      _action_cache[permission] = action.id
    except Action.DoesNotExist:
      return False
      
  if _action_cache[permission] == -1: return False
  
  for aro in user.get_access_request_objects():
    new = check_permission(aro, aco, _action_cache[permission])
    a_current = abs(current)
    a_new = abs(new)
    if a_new > a_current: current = new
    if a_new == a_current:
      if new != current: raise Exception, "Inconsistent ACL for user %s on %s" % (user, aco)
  return current > 0

def has_named_permission(user, perm):
  parts = perm.split(".")
  return has_permission(user, ACO.objects.match_path(parts[:-1]), parts[-1])